package system

import (
	"log"
	"sync"
	"time"

	"github.com/asynkron/protoactor-go/actor"
)

// MetricsCollector is an actor that collects system metrics
type MetricsCollector struct {
	// metrics are the collected metrics
	metrics map[string]*MetricValue
	// subscribers are the actors subscribed to metrics updates
	subscribers []*actor.PID
	// mu is a mutex for protecting the metrics
	mu sync.RWMutex
	// logger is the metrics collector logger
	logger *log.Logger
	// collectionInterval is the interval between metric collections
	collectionInterval time.Duration
}

// MetricValue represents a metric value
type MetricValue struct {
	// Name is the metric name
	Name string
	// Value is the metric value
	Value float64
	// Tags are the metric tags
	Tags map[string]string
	// Timestamp is when the metric was collected
	Timestamp time.Time
}

// NewMetricsCollector creates a new metrics collector
func NewMetricsCollector() actor.Actor {
	return &MetricsCollector{
		metrics:            make(map[string]*MetricValue),
		subscribers:        make([]*actor.PID, 0),
		logger:             log.New(log.Writer(), "[MetricsCollector] ", log.LstdFlags),
		collectionInterval: 10 * time.Second,
	}
}

// Receive handles messages for the metrics collector
func (m *MetricsCollector) Receive(context actor.Context) {
	switch msg := context.Message().(type) {
	case *actor.Started:
		m.handleStarted(context)
	case *actor.Stopping:
		m.handleStopping(context)
	case *ReportMetricCommand:
		m.handleReportMetric(context, msg)
	case *GetMetricsCommand:
		m.handleGetMetrics(context)
	case *SubscribeMetricsCommand:
		m.handleSubscribeMetrics(context)
	case *UnsubscribeMetricsCommand:
		m.handleUnsubscribeMetrics(context)
	case *CollectMetricsMessage:
		m.handleCollectMetrics(context)
	default:
		m.logger.Printf("Received unknown message: %T", msg)
	}
}

// handleStarted handles the Started message
func (m *MetricsCollector) handleStarted(context actor.Context) {
	m.logger.Println("Metrics collector started")

	// Start metrics collection
	context.Send(context.Self(), &CollectMetricsMessage{})
}

// handleStopping handles the Stopping message
func (m *MetricsCollector) handleStopping(context actor.Context) {
	m.logger.Println("Metrics collector stopping")
}

// handleReportMetric handles the ReportMetricCommand message
func (m *MetricsCollector) handleReportMetric(context actor.Context, msg *ReportMetricCommand) {
	m.mu.Lock()
	defer m.mu.Unlock()

	// Create metric key
	key := msg.Name
	if len(msg.Tags) > 0 {
		for k, v := range msg.Tags {
			key += ":" + k + "=" + v
		}
	}

	// Store metric
	m.metrics[key] = &MetricValue{
		Name:      msg.Name,
		Value:     msg.Value,
		Tags:      msg.Tags,
		Timestamp: time.Now(),
	}

	// Respond with success
	context.Respond(&ReportMetricResponse{
		Success: true,
	})

	m.logger.Printf("Reported metric: %s = %f", key, msg.Value)
}

// handleGetMetrics handles the GetMetricsCommand message
func (m *MetricsCollector) handleGetMetrics(context actor.Context) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	// Create a copy of the metrics
	metrics := make(map[string]*MetricValue)
	for k, v := range m.metrics {
		metricCopy := *v
		metrics[k] = &metricCopy
	}

	// Respond with metrics
	context.Respond(&GetMetricsResponse{
		Metrics: metrics,
	})
}

// handleSubscribeMetrics handles the SubscribeMetricsCommand message
func (m *MetricsCollector) handleSubscribeMetrics(context actor.Context) {
	m.mu.Lock()
	defer m.mu.Unlock()

	// Add subscriber
	m.subscribers = append(m.subscribers, context.Sender())

	// Respond with success
	context.Respond(&SubscribeMetricsResponse{
		Success: true,
	})

	m.logger.Printf("Added metrics subscriber: %s", context.Sender().String())
}

// handleUnsubscribeMetrics handles the UnsubscribeMetricsCommand message
func (m *MetricsCollector) handleUnsubscribeMetrics(context actor.Context) {
	m.mu.Lock()
	defer m.mu.Unlock()

	// Find and remove subscriber
	for i, pid := range m.subscribers {
		if pid.String() == context.Sender().String() {
			m.subscribers = append(m.subscribers[:i], m.subscribers[i+1:]...)
			break
		}
	}

	// Respond with success
	context.Respond(&UnsubscribeMetricsResponse{
		Success: true,
	})

	m.logger.Printf("Removed metrics subscriber: %s", context.Sender().String())
}

// handleCollectMetrics handles the CollectMetricsMessage message
func (m *MetricsCollector) handleCollectMetrics(context actor.Context) {
	m.mu.Lock()
	defer m.mu.Unlock()

	// Collect system metrics
	m.collectSystemMetrics()

	// Notify subscribers
	m.notifySubscribers(context)

	// Schedule next collection
	time.AfterFunc(m.collectionInterval, func() {
		context.Send(context.Self(), &CollectMetricsMessage{})
	})
}

// collectSystemMetrics collects system metrics
func (m *MetricsCollector) collectSystemMetrics() {
	// Collect actor system metrics
	m.metrics["system.actors.count"] = &MetricValue{
		Name:      "system.actors.count",
		Value:     float64(0), // TODO: Get actual actor count
		Timestamp: time.Now(),
	}

	m.metrics["system.messages.processed"] = &MetricValue{
		Name:      "system.messages.processed",
		Value:     float64(0), // TODO: Get actual message count
		Timestamp: time.Now(),
	}

	// Collect memory metrics
	m.metrics["system.memory.used"] = &MetricValue{
		Name:      "system.memory.used",
		Value:     float64(0), // TODO: Get actual memory usage
		Timestamp: time.Now(),
	}

	// Collect CPU metrics
	m.metrics["system.cpu.usage"] = &MetricValue{
		Name:      "system.cpu.usage",
		Value:     float64(0), // TODO: Get actual CPU usage
		Timestamp: time.Now(),
	}
}

// notifySubscribers notifies subscribers of metrics updates
func (m *MetricsCollector) notifySubscribers(context actor.Context) {
	if len(m.subscribers) == 0 {
		return
	}

	// Create a copy of the metrics
	metrics := make(map[string]*MetricValue)
	for k, v := range m.metrics {
		metricCopy := *v
		metrics[k] = &metricCopy
	}

	// Create event
	event := &MetricsUpdatedEvent{
		Metrics:   metrics,
		Timestamp: time.Now(),
	}

	// Notify subscribers
	for _, pid := range m.subscribers {
		context.Send(pid, event)
	}
}

// ReportMetricCommand is a command to report a metric
type ReportMetricCommand struct {
	// Name is the metric name
	Name string
	// Value is the metric value
	Value float64
	// Tags are the metric tags
	Tags map[string]string
}

// ReportMetricResponse is the response to a ReportMetricCommand
type ReportMetricResponse struct {
	// Success indicates if the operation was successful
	Success bool
	// Error is the error message if the operation failed
	Error string
}

// GetMetricsCommand is a command to get metrics
type GetMetricsCommand struct {
	// Filter is an optional filter for metrics
	Filter string
}

// GetMetricsResponse is the response to a GetMetricsCommand
type GetMetricsResponse struct {
	// Metrics are the metrics
	Metrics map[string]*MetricValue
}

// SubscribeMetricsCommand is a command to subscribe to metrics updates
type SubscribeMetricsCommand struct{}

// SubscribeMetricsResponse is the response to a SubscribeMetricsCommand
type SubscribeMetricsResponse struct {
	// Success indicates if the operation was successful
	Success bool
	// Error is the error message if the operation failed
	Error string
}

// UnsubscribeMetricsCommand is a command to unsubscribe from metrics updates
type UnsubscribeMetricsCommand struct{}

// UnsubscribeMetricsResponse is the response to an UnsubscribeMetricsCommand
type UnsubscribeMetricsResponse struct {
	// Success indicates if the operation was successful
	Success bool
	// Error is the error message if the operation failed
	Error string
}

// MetricsUpdatedEvent is an event for metrics updates
type MetricsUpdatedEvent struct {
	// Metrics are the updated metrics
	Metrics map[string]*MetricValue
	// Timestamp is when the metrics were updated
	Timestamp time.Time
}

// CollectMetricsMessage is a message for collecting metrics
type CollectMetricsMessage struct{}
